#pragma once

#include "Common/Config.h"
#include "Linux/Utils/ELFContainer.h"
#include "Linux/Utils/ELFSymbolDatabase.h"

#include <array>
#include <bitset>
#include <cassert>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <sys/mman.h>
#include <sys/user.h>

#include <FEXCore/Core/CodeLoader.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/X86Enums.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/BitUtils.h>
#include <FEXCore/Utils/CompilerDefs.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/fextl/map.h>
#include <FEXCore/fextl/vector.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <FEXHeaderUtils/TypeDefines.h>

#include <fmt/format.h>

namespace FEX::HarnessHelper {
  inline bool CompareStates(FEXCore::Core::CPUState const& State1,
                            FEXCore::Core::CPUState const& State2,
                            uint64_t MatchMask,
                            bool OutputGPRs,
                            bool SupportsAVX) {
    bool Matches = true;

    const auto DumpGPRs = [OutputGPRs](const std::string& Name, uint64_t A, uint64_t B) {
      if (!OutputGPRs) {
        return;
      }
      if (A == B) {
        return;
      }

      fmt::print("{}: 0x{:016x} {} 0x{:016x}\n", Name, A, A==B ? "==" : "!=", B);
    };

    const auto DumpFLAGs = [OutputGPRs](const std::string& Name, uint64_t A, uint64_t B) {
      if (!OutputGPRs) {
        return;
      }
      if (A == B) {
        return;
      }

      static constexpr std::array<uint32_t, 17> Flags = {
        FEXCore::X86State::RFLAG_CF_LOC,
        FEXCore::X86State::RFLAG_PF_LOC,
        FEXCore::X86State::RFLAG_AF_LOC,
        FEXCore::X86State::RFLAG_ZF_LOC,
        FEXCore::X86State::RFLAG_SF_LOC,
        FEXCore::X86State::RFLAG_TF_LOC,
        FEXCore::X86State::RFLAG_IF_LOC,
        FEXCore::X86State::RFLAG_DF_LOC,
        FEXCore::X86State::RFLAG_OF_LOC,
        FEXCore::X86State::RFLAG_IOPL_LOC,
        FEXCore::X86State::RFLAG_NT_LOC,
        FEXCore::X86State::RFLAG_RF_LOC,
        FEXCore::X86State::RFLAG_VM_LOC,
        FEXCore::X86State::RFLAG_AC_LOC,
        FEXCore::X86State::RFLAG_VIF_LOC,
        FEXCore::X86State::RFLAG_VIP_LOC,
        FEXCore::X86State::RFLAG_ID_LOC,
      };

      fmt::print("{}: 0x{:016x} {} 0x{:016x}\n", Name, A, A==B ? "==" : "!=", B);
      for (const auto Flag : Flags) {
        const auto FlagMask = uint64_t{1} << Flag;
        if ((A & FlagMask) != (B & FlagMask)) {
          fmt::print("\t{}: {} != {}\n", FEXCore::Core::GetFlagName(Flag), (A >> Flag) & 1, (B >> Flag) & 1);
        }
      }
    };

    const auto CheckGPRs = [&Matches, DumpGPRs](const std::string& Name, uint64_t A, uint64_t B){
      DumpGPRs(Name, A, B);
      Matches &= A == B;
    };

    const auto CheckFLAGS = [&Matches, DumpFLAGs](const std::string& Name, uint64_t A, uint64_t B){
      DumpFLAGs(Name, A, B);
      Matches &= A == B;
    };


    // RIP
    if (MatchMask & 1) {
      CheckGPRs("RIP", State1.rip, State2.rip);
    }

    MatchMask >>= 1;

    // GPRS
    for (unsigned i = 0; i < FEXCore::Core::CPUState::NUM_GPRS; ++i, MatchMask >>= 1) {
      if (MatchMask & 1) {
        CheckGPRs("GPR" + std::to_string(i), State1.gregs[i], State2.gregs[i]);
      }
    }

    // XMM
    if (SupportsAVX) {
      for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i, MatchMask >>= 1) {
        if (MatchMask & 1) {
          CheckGPRs("XMM0_" + std::to_string(i), State1.xmm.avx.data[i][0], State2.xmm.avx.data[i][0]);
          CheckGPRs("XMM1_" + std::to_string(i), State1.xmm.avx.data[i][1], State2.xmm.avx.data[i][1]);
        }
      }
    } else {
      for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i, MatchMask >>= 1) {
        if (MatchMask & 1) {
          CheckGPRs("XMM0_" + std::to_string(i), State1.xmm.sse.data[i][0], State2.xmm.sse.data[i][0]);
          CheckGPRs("XMM1_" + std::to_string(i), State1.xmm.sse.data[i][1], State2.xmm.sse.data[i][1]);
        }
      }
    }

    // GS
    if (MatchMask & 1) {
      CheckGPRs("GS", State1.gs_cached, State2.gs_cached);
    }
    MatchMask >>= 1;

    // FS
    if (MatchMask & 1) {
      CheckGPRs("FS", State1.fs_cached, State2.fs_cached);
    }
    MatchMask >>= 1;

    auto CompactRFlags = [](auto Arg) -> uint32_t {
      uint32_t Res = 2;
      for (int i = 0; i < 32; ++i) {
        Res |= Arg->flags[i] << i;
      }
      return Res;
    };

    // FLAGS
    if (MatchMask & 1) {
      uint32_t rflags1 = CompactRFlags(&State1);
      uint32_t rflags2 = CompactRFlags(&State2);

      CheckFLAGS("FLAGS", rflags1, rflags2);
    }
    MatchMask >>= 1;
    return Matches;
  }

  inline void ReadFile(std::string const &Filename, fextl::vector<char> *Data) {
    std::fstream TestFile(Filename, std::fstream::in | std::fstream::binary);
    LOGMAN_THROW_A_FMT(TestFile.is_open(), "Failed to open file");

    TestFile.seekg(0, std::fstream::end);
    const size_t FileSize = TestFile.tellg();
    TestFile.seekg(0, std::fstream::beg);

    Data->resize(FileSize);

    TestFile.read(Data->data(), FileSize);
  }

  class ConfigLoader final {
  public:
    void Init(std::string const &ConfigFilename) {
      ReadFile(ConfigFilename, &RawConfigFile);
      memcpy(&BaseConfig, RawConfigFile.data(), sizeof(ConfigStructBase));
      GetEnvironmentOptions();
    }

    fextl::vector<std::pair<std::string_view, std::string_view>> GetEnvironmentOptions() {
      fextl::vector<std::pair<std::string_view, std::string_view>> Env{};

      uintptr_t DataOffset = BaseConfig.OptionEnvOptionOffset;
      for (unsigned i = 0; i < BaseConfig.OptionEnvOptionCount; ++i) {
        // Environment variables are null terminated strings
        std::string_view Key = RawConfigFile.data() + DataOffset;
        std::string_view Value = RawConfigFile.data() + DataOffset + Key.size() + 1;
        DataOffset += Key.size() + Value.size() + 2;
        Env.emplace_back(Key, Value);
      }
      return Env;
    }

    bool CompareStates(FEXCore::Core::CPUState const* State1, FEXCore::Core::CPUState const* State2, bool SupportsAVX) {
      bool Matches = true;
      uint64_t MatchMask = BaseConfig.OptionMatch & ~BaseConfig.OptionIgnore;
      if (State1 && State2) {
        Matches &= FEX::HarnessHelper::CompareStates(*State1, *State2, MatchMask, ConfigDumpGPRs(), SupportsAVX);
      }

      if (BaseConfig.OptionRegDataCount > 0) {
        static constexpr std::array<uint64_t, 45> OffsetArrayAVX = {{
          offsetof(FEXCore::Core::CPUState, rip),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RAX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RBX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RCX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RDX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSI]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RDI]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RBP]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSP]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R8]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R9]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R10]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R11]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R12]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R13]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R14]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R15]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[0][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[1][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[2][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[3][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[4][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[5][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[6][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[7][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[8][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[9][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[10][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[11][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[12][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[13][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[14][0]),
          offsetof(FEXCore::Core::CPUState, xmm.avx.data[15][0]),
          offsetof(FEXCore::Core::CPUState, gs_cached),
          offsetof(FEXCore::Core::CPUState, fs_cached),
          offsetof(FEXCore::Core::CPUState, flags),
          offsetof(FEXCore::Core::CPUState, mm[0][0]),
          offsetof(FEXCore::Core::CPUState, mm[1][0]),
          offsetof(FEXCore::Core::CPUState, mm[2][0]),
          offsetof(FEXCore::Core::CPUState, mm[3][0]),
          offsetof(FEXCore::Core::CPUState, mm[4][0]),
          offsetof(FEXCore::Core::CPUState, mm[5][0]),
          offsetof(FEXCore::Core::CPUState, mm[6][0]),
          offsetof(FEXCore::Core::CPUState, mm[7][0]),
          offsetof(FEXCore::Core::CPUState, mm[8][0]),
        }};
        static constexpr std::array<uint64_t, 45> OffsetArraySSE = {{
          offsetof(FEXCore::Core::CPUState, rip),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RAX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RBX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RCX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RDX]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSI]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RDI]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RBP]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_RSP]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R8]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R9]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R10]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R11]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R12]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R13]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R14]),
          offsetof(FEXCore::Core::CPUState, gregs[FEXCore::X86State::REG_R15]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[0][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[1][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[2][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[3][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[4][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[5][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[6][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[7][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[8][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[9][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[10][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[11][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[12][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[13][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[14][0]),
          offsetof(FEXCore::Core::CPUState, xmm.sse.data[15][0]),
          offsetof(FEXCore::Core::CPUState, gs_cached),
          offsetof(FEXCore::Core::CPUState, fs_cached),
          offsetof(FEXCore::Core::CPUState, flags),
          offsetof(FEXCore::Core::CPUState, mm[0][0]),
          offsetof(FEXCore::Core::CPUState, mm[1][0]),
          offsetof(FEXCore::Core::CPUState, mm[2][0]),
          offsetof(FEXCore::Core::CPUState, mm[3][0]),
          offsetof(FEXCore::Core::CPUState, mm[4][0]),
          offsetof(FEXCore::Core::CPUState, mm[5][0]),
          offsetof(FEXCore::Core::CPUState, mm[6][0]),
          offsetof(FEXCore::Core::CPUState, mm[7][0]),
          offsetof(FEXCore::Core::CPUState, mm[8][0]),
        }};

        uintptr_t DataOffset = BaseConfig.OptionRegDataOffset;
        for (unsigned i = 0; i < BaseConfig.OptionRegDataCount; ++i) {
          RegDataStructBase *RegData = reinterpret_cast<RegDataStructBase*>(RawConfigFile.data() + DataOffset);
          [[maybe_unused]] std::bitset<64> RegFlags = RegData->RegKey;
          assert(RegFlags.count() == 1  && "Must set reg data explicitly per register");

          size_t NameIndex = FEXCore::FindFirstSetBit(RegData->RegKey) - 1;
          auto Offset = SupportsAVX ? OffsetArrayAVX[NameIndex] : OffsetArraySSE[NameIndex];
          uint64_t *State1Data = reinterpret_cast<uint64_t*>(reinterpret_cast<uint64_t>(State1) + Offset);
          uint64_t *State2Data = reinterpret_cast<uint64_t*>(reinterpret_cast<uint64_t>(State2) + Offset);

          const auto DumpGPRs = [this](const std::string& Name, uint64_t A, uint64_t B) {
            if (!ConfigDumpGPRs()) {
              return;
            }

            fmt::print("{}: 0x{:016x} {} 0x{:016x} (Expected)\n", Name, A, A==B ? "==" : "!=", B);
          };

          const auto CheckGPRs = [&Matches, DumpGPRs](const std::string& Name, uint64_t A, uint64_t B) {
            DumpGPRs(Name, A, B);
            Matches &= A == B;
          };

          for (size_t j = 0; j < RegData->RegDataCount; ++j) {
            std::string Name;
            if (NameIndex == 0) // RIP
              Name = "RIP";
            else if (NameIndex >= 1 && NameIndex < 17)
              Name = fmt::format("GPR{}", NameIndex - 1);
            else if (NameIndex >= 17 && NameIndex < 33)
              Name = fmt::format("XMM[{}][{}]", NameIndex - 17, j);
            else if (NameIndex == 33)
              Name = "gs";
            else if (NameIndex == 34)
              Name ="fs";
            else if (NameIndex == 35)
              Name = "rflags";
            else if (NameIndex >= 36 && NameIndex < 45)
              Name = fmt::format("MM[{}][{}]", NameIndex - 36, j);

            if (State1) {
              CheckGPRs(fmt::format("Core1: {}: ", Name), State1Data[j], RegData->RegValues[j]);
            }
            if (State2) {
              CheckGPRs(fmt::format("Core2: {}: ", Name), State2Data[j], RegData->RegValues[j]);
            }
          }

          // Get the correct data offset
          DataOffset += sizeof(RegDataStructBase) + RegData->RegDataCount * 8;
        }
      }
      return Matches;
    }

    fextl::map<uintptr_t, size_t> GetMemoryRegions() {
      fextl::map<uintptr_t, size_t> regions;

      uintptr_t DataOffset = BaseConfig.OptionMemoryRegionOffset;
      for (unsigned i = 0; i < BaseConfig.OptionMemoryRegionCount; ++i) {
        MemoryRegionBase *Region = reinterpret_cast<MemoryRegionBase*>(RawConfigFile.data() + DataOffset);
        regions[Region->Region] = Region->Size;

        DataOffset += sizeof(MemoryRegionBase);
      }

      return regions;
    }

    void LoadMemory() {
      uintptr_t DataOffset = BaseConfig.OptionMemDataOffset;
      for (unsigned i = 0; i < BaseConfig.OptionMemDataCount; ++i) {
        MemDataStructBase *MemData = reinterpret_cast<MemDataStructBase*>(RawConfigFile.data() + DataOffset);
        memcpy(reinterpret_cast<void*>(MemData->address), &MemData->data, MemData->length);
        DataOffset += sizeof(MemDataStructBase) + MemData->length;
      }
    }

    bool Is64BitMode() const { return BaseConfig.OptionMode == 1; }

    enum HostFeatures {
      FEATURE_ANY    = 0,
      FEATURE_3DNOW  = (1 << 0),
      FEATURE_SSE4A  = (1 << 1),
      FEATURE_AVX    = (1 << 2),
      FEATURE_RAND   = (1 << 3),
      FEATURE_SHA    = (1 << 4),
      FEATURE_CLZERO = (1 << 5),
      FEATURE_BMI1   = (1 << 6),
      FEATURE_BMI2   = (1 << 7),
      FEATURE_CLWB   = (1 << 8),
    };

    bool Requires3DNow()  const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_3DNOW; }
    bool RequiresSSE4A()  const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_SSE4A; }
    bool RequiresAVX()    const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_AVX; }
    bool RequiresRAND()   const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_RAND; }
    bool RequiresSHA()    const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_SHA; }
    bool RequiresCLZERO() const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_CLZERO; }
    bool RequiresBMI1()   const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_BMI1; }
    bool RequiresBMI2()   const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_BMI2; }
    bool RequiresCLWB()   const { return BaseConfig.OptionHostFeatures & HostFeatures::FEATURE_CLWB; }

  private:
    FEX_CONFIG_OPT(ConfigDumpGPRs, DUMPGPRS);

    struct ConfigStructBase {
      uint64_t OptionMatch;
      uint64_t OptionIgnore;
      uint64_t OptionStackSize;
      uint64_t OptionEntryPoint;
      uint32_t OptionABI;
      uint32_t OptionMode;
      uint32_t OptionHostFeatures;
      uint32_t OptionMemoryRegionOffset;
      uint32_t OptionMemoryRegionCount;
      uint32_t OptionRegDataOffset;
      uint32_t OptionRegDataCount;
      uint32_t OptionMemDataOffset;
      uint32_t OptionMemDataCount;
      uint32_t OptionEnvOptionOffset;
      uint32_t OptionEnvOptionCount;
      uint8_t  AdditionalData[];
    } FEX_PACKED;

    struct MemoryRegionBase {
      uint64_t Region;
      uint64_t Size;
    } FEX_PACKED;

    struct RegDataStructBase {
      uint32_t RegDataCount;
      uint64_t RegKey;
      uint64_t RegValues[];
    } FEX_PACKED;

    struct MemDataStructBase {
      uint64_t address;
      uint32_t length;
      uint8_t data[];
    } FEX_PACKED;

    fextl::vector<char> RawConfigFile;
    ConfigStructBase BaseConfig;
  };

  class HarnessCodeLoader final : public FEXCore::CodeLoader {
  public:

    HarnessCodeLoader(std::string const &Filename, const char *ConfigFilename) {
      TestFD = open(Filename.c_str(), O_CLOEXEC | O_RDONLY);
      TestFileSize = lseek(TestFD, 0, SEEK_END);
      lseek(TestFD, 0, SEEK_SET);

      if (ConfigFilename) {
        Config.Init(ConfigFilename);
      }
    }

    uint64_t StackSize() const override {
      return STACK_SIZE;
    }

    uint64_t GetStackPointer() override {
      if (Config.Is64BitMode()) {
        return reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(nullptr, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) + STACK_SIZE;
      }
      else {
        uint64_t Result = reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(reinterpret_cast<void*>(STACK_OFFSET), STACK_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
        LOGMAN_THROW_AA_FMT(Result != ~0ULL, "Stack Pointer mmap failed");
        return Result + STACK_SIZE;
      }
    }

    uint64_t DefaultRIP() const override {
      return RIP;
    }

    bool MapMemory(const MapperFn& Mapper, const UnmapperFn& Unmapper) override {
      bool LimitedSize = true;
      auto DoMMap = [&Mapper](uint64_t Address, size_t Size) -> void* {
        void *Result = Mapper(reinterpret_cast<void*>(Address), Size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        LOGMAN_THROW_AA_FMT(Result == reinterpret_cast<void*>(Address), "Map Memory mmap failed");
        return Result;
      };

      if (LimitedSize) {
        DoMMap(0xe000'0000, FHU::FEX_PAGE_SIZE * 10);

        // SIB8
        // We test [-128, -126] (Bottom)
        // We test [-8, 8] (Middle)
        // We test [120, 127] (Top)
        // Can fit in two pages
        DoMMap(0xe800'0000 - FHU::FEX_PAGE_SIZE, FHU::FEX_PAGE_SIZE * 2);
      }
      else {
        // This is scratch memory location and SIB8 location
        DoMMap(0xe000'0000, 0x1000'0000);
        // This is for large SIB 32bit displacement testing
        DoMMap(0x2'0000'0000, 0x1'0000'1000);
      }

      // Map in the memory region for the test file
      size_t Length = FEXCore::AlignUp(TestFileSize, FHU::FEX_PAGE_SIZE);
      Mapper(reinterpret_cast<void*>(Code_start_page), Length, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE, TestFD, 0);
      RIP = Code_start_page;

      // Map the memory regions the test file asks for
      for (auto& [region, size] : Config.GetMemoryRegions()) {
        DoMMap(region, size);
      }

      LoadMemory();

      return true;
    }

    void LoadMemory() {
      // Memory base here starts at the start location we passed back with GetLayout()
      // This will write at [CODE_START_RANGE + 0, RawFile.size() )
      Config.LoadMemory();
    }

    fextl::vector<std::pair<std::string_view, std::string_view>> GetEnvironmentOptions() {
      return Config.GetEnvironmentOptions();
    }

    bool CompareStates(FEXCore::Core::CPUState const* State1, FEXCore::Core::CPUState const* State2, bool SupportsAVX) {
      return Config.CompareStates(State1, State2, SupportsAVX);
    }

    bool Is64BitMode() const { return Config.Is64BitMode(); }
    bool Requires3DNow()  const { return Config.Requires3DNow(); }
    bool RequiresSSE4A()  const { return Config.RequiresSSE4A(); }
    bool RequiresAVX()    const { return Config.RequiresAVX(); }
    bool RequiresRAND()   const { return Config.RequiresRAND(); }
    bool RequiresSHA()    const { return Config.RequiresSHA(); }
    bool RequiresCLZERO() const { return Config.RequiresCLZERO(); }
    bool RequiresBMI1()   const { return Config.RequiresBMI1(); }
    bool RequiresBMI2()   const { return Config.RequiresBMI2(); }
    bool RequiresCLWB()   const { return Config.RequiresCLWB(); }

  private:
    constexpr static uint64_t STACK_SIZE = FHU::FEX_PAGE_SIZE;
    constexpr static uint64_t STACK_OFFSET = 0xc000'0000;
    // Zero is special case to know when we are done
    uint64_t Code_start_page = 0x1'0000;
    uint64_t RIP {};

    int TestFD{};
    size_t TestFileSize{};
    ConfigLoader Config;
  };

}
